﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Qadir.Collections.Generic;

namespace Qadir.Text
{
    /// <summary>
    /// A class containing additional Regex functions.
    /// </summary>
    public static class RegularExpressions
    {
        /// <summary>
        /// Escapes the 12 special Regex characters within a string and returns the new string.
        /// </summary>
        /// <param name="input">The string to be escaped.</param>
        /// <returns>Returns a Regex-escaped string.</returns>
        public static string Escape (string input)
        {
            return input.Replace(@"\", @"\\").Replace("^", @"\^").Replace("$", @"\$").Replace(".", @"\.").Replace("|", @"\|").Replace("?", @"\?").Replace("*", @"\*").Replace("+", @"\+").Replace("(", @"\(").Replace(")", @"\)").Replace("[", @"\[").Replace("{", @"\{");
        }

        /// <summary>
        /// Escapes 11 of the 12 special Regex characters within a string and returns the new string.
        /// </summary>
        /// <param name="input">The string to be escaped.</param>
        /// <returns>Returns a Regex-escaped string.</returns>
        public static string EscapeForFilter(string input)
        {
            return input.Replace(@"\", @"\\").Replace("^", @"\^").Replace("$", @"\$").Replace(".", @"\.").Replace("|", @"\|").Replace("?", @"\?").Replace("*", @"\*").Replace("+", @"\+").Replace("(", @"\(").Replace(")", @"\)").Replace("[", @"\[");//.Replace("{", @"\{");
        }

        /// <summary>
        /// Attempts to parse data out of a string using Regular Expressions and a filter format string.
        /// </summary>
        /// <param name="source">The source string to parse.</param>
        /// <param name="format">The filter format string to use. Ex: "The quick, brown {animal1} jumps over the lazy {animal2}."</param>
        /// <param name="searchPattern">A Regex search pattern used to search the source string for text. This string should be a Regex grouping statement. Ex: (\w|\W)</param>
        /// <param name="options">Any additional Regex options to use for this search.</param>
        /// <param name="autoEscape">A boolean value indicating whether to automatically escape special characters.</param>
        /// <returns>Returns a populated dictionary with any and all text filtered out. Ex: animal1 => "fox", animal2 => "dog"</returns>
        public static Dictionary<string, string> Filter(string source, string format, bool autoEscape = true, RegexOptions options = RegexOptions.None)
        {
            Dictionary<string, string> filtered = new Dictionary<string, string>();

            string searchPattern = "(\\w+|\\W+)";
            string searcher = format;
            if (autoEscape)
                searcher = EscapeForFilter(searcher);

            Manager<string> keys = new Manager<string>();
            foreach (Match filterMatch in Regex.Matches(format, "{([^}]+)}", options)) //{animal1}
            {
                keys.Add(filterMatch.Groups[1].Value); //animal1
                int index = filterMatch.Index;
                if (index + filterMatch.Length < format.Length)
                {
                    char next = format[index + filterMatch.Length];
                    string pattern = string.Format("([^{0}]+)", Escape(new string(next, 1)));
                    searcher = searcher.Replace(filterMatch.Value, pattern); //Replace "{animal1}" with "([^\.]+)"
                }
                else
                    searcher = searcher.Replace(filterMatch.Value, searchPattern); //Replace "{animal1}" with "(\w|\W)"
            }
            foreach (Match search in Regex.Matches(source, searcher, options))
            {
                for (int i = 0; i < search.Groups.Count; i++)
                {
                    try
                    {
                        string val = search.Groups[i + 1].Value;
                        filtered.Add(keys[i], val); //Add animal1 => "fox"
                    }
                    catch
                    {
                    }
                }
            }
            return filtered;
        }
    }

    /// <summary>
    /// Functions to be run on strings to make many operations easier and faster.
    /// </summary>
    public static class StringFunctions
    {
        /// <summary>
        /// Parses a string and attempts to extract strings from it, according to a format.
        /// </summary>
        /// <param name="input">The input string.</param>
        /// <param name="format">The format to parse with.</param>
        /// <returns>Returns a string populated with values. Values that could not be found are null.</returns>
        public static string[] ParseFormat(string input, string format)
        {
            List<string> f = new List<string>();
            for (int i = 0; i > -1; i++)
            {
                string formatter = "{" + i.ToString() + "}";
                if (!format.Contains(formatter))
                {
                    break;
                }
                int index = format.IndexOf(formatter);
                string before = format.Substring(0, index);
                int bar = before.LastIndexOf((i - 1).ToString() + "}");
                if (bar != -1)
                {
                    before = before.Substring(bar + 1 + (1 + i).ToString().Length);
                }
                string after = format.Substring(index + formatter.Length);
                int foo = after.IndexOf("{" + (i + 1).ToString());
                if (foo != -1)
                {
                    after = after.Substring(0, foo);
                }
                if (!(input.Contains(before) && input.Contains(after)))
                {
                    f.Add(null);
                }
                int beforeIndex = input.IndexOf(before);
                int afterIndex = input.LastIndexOf(after);
                int start = beforeIndex + before.Length;
                int length = afterIndex - start;
                try
                {
                    string ou = input.Substring(start, length);
                    if (ou.Length > 1)
                    {
                        f.Add(ou);
                    }
                    else
                    {
                        f.Add(null);
                    }
                }
                catch
                {
                    f.Add(null);
                }
            }
            return f.ToArray();
        }

        /// <summary>
        /// Returns a random string of a length less than or equal to the specified maximum length.
        /// </summary>
        /// <param name="maxlength">The maximum length the resultant string will be.</param>
        /// <param name="validCharacters">A string containing all possible characters the resultant string should contain.</param>
        /// <param name="random">A System.Random that will be used to generate the string. By default, this is null.</param>
        /// <returns>Returns a random string generated with the supplied arguments.s</returns>
        public static string RandomString(int maxlength, string validCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!.", Random random = null)
        {
            Random rand;
            if (random != null)
                rand = random;
            else
                rand = new Random();
            string result = string.Empty;
            int length = rand.Next(1, maxlength);
            while (result.Length < length)
            {
                result += validCharacters[rand.Next(0, validCharacters.Length)];
            }
            return result;
        }
    }
}
